/*
 * File:                MainForm.cs
 * Authors:             Jonathan T. McClain, Travis L. Bauer
 * Company:             Sandia National Laboratories
 * Project:             MVC
 *
 * Copyright May 5, 2008, Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
 * or on behalf of the U.S. Government. Export of this program may require a
 * license from the United States Government. See CopyrightHistory.txt for
 * complete details.
 *
 * Reviewers:
 * Review Date:
 * Review Comments:
 *
 * Revision History:
 *
 * $Log: MainForm.cs,v $
 * Revision 1.32  2009/03/11 23:03:43  jtmccl
 * Added a splash screen.  Bug #801 [JTM 20090311]
 * Plugins can now be loaded from the task bar context menu. [JTM 20090311]
 * Added an option to reset the local plugins and restart the application. [JTM 20090310]
 * Added an option to restart the application after changing settings. [JTM 20090310]
 * Fixed an issue where failures during the sync process would cause MVCShell to be unusable.  Bug #1267 [JTM 20090310]
 *
 * Revision 1.31  2008/08/01 20:56:36  jtmccl
 * Fixed an issue where MVCShell would not remember the window state between sessions.
 *
 * Revision 1.30  2008/06/24 20:33:38  jtmccl
 * Fix for bug #1065.
 *
 * Revision 1.29  2008/05/08 21:09:51  jtmccl
 * Added some comments.
 *
 * Revision 1.28  2008/05/08 14:45:10  jtmccl
 * The invalid plug-in source path notification is now a message box
 * instead of a tool tip bubble to prevent an issue of overlapping tool tip
 * bubbles.
 *
 * Revision 1.27  2008/05/06 22:20:02  jtmccl
 * Fixed an issue where plug-ins were not loaded if the plug-ins were up to
 * date.
 *
 * Revision 1.26  2008/05/05 22:50:17  jtmccl
 * Fixed an issue where plug-ins could be loaded twice.
 *
 */

using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using Microsoft.Synchronization.Files;
using Sandia.MVCFramework.Common;
using Sandia.MVCFramework.Controller;
using Sandia.MVCFramework.Plugin;
using Sandia.MVCFramework.Request;
using Sandia.MVCFramework.View;
using Sandia.MVCFramework.ViewManager;
using ApplicationInfoPlugin;
using Sandia.MVCShell.Properties;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace Sandia.MVCShell
{
    /// <summary>
    /// The main form for MVCShell.
    /// </summary>
    public partial class MainForm : Form, IRequestProcessor
    {
        /// <summary>
        /// A mapping of plugins to the tab pages on which they reside. Note
        /// that this is not necessarily all of the plugins, only those which
        /// have a ViewManager that is a control.
        /// </summary>
        private Dictionary<string, List<TabPage>> pluginPages = 
            new Dictionary<string, List<TabPage>>();

        /// <summary>
        /// The internal plugin loader.
        /// </summary>
        private StandardPluginLoader pluginLoader;

        /// <summary>
        /// The internal controller.
        /// </summary>
        private StandardController controller = new StandardController();

        /// <summary>
        /// The internal plugin source.
        /// </summary>
        private StandardPluginSource pluginSource = null;

        /// <summary>
        /// A mapping of IDs to persistent forms.
        /// </summary>
        private SortedList<string, Form> persistentViewForms = 
            new SortedList<string, Form>();

        /// <summary>
        /// A list of plugins to load on startup.
        /// </summary>
        private List<string> pluginsToLoad = new List<string>();

        /// <summary>
        /// A dictionary of the current active plugins.
        /// </summary>
        private Dictionary<string, IPlugin> activePlugins = 
            new Dictionary<string, IPlugin>();

        /// <summary>
        /// The flyout component used to control which plugins are enabled.
        /// </summary>
        private PluginControl flyoutPluginControl;

        /// <summary>
        /// The window component used to control which plugins are enabled.
        /// </summary>
        private PluginControl windowPluginControl;

        /// <summary>
        /// The window that contains window plugin control.
        /// </summary>
        private Form windowPluginControlForm = new Form();

        /// <summary>
        /// A split panel used to house the plugin control when it is pinned.
        /// </summary>
        private SplitContainer splitPanel;

        /// <summary>
        /// A form used to indicate the progress of a plug-in update.
        /// </summary>
        private SynchronizationProgressBarForm syncProgressForm =
            new SynchronizationProgressBarForm();

        /// <summary>
        /// A delegate used for events related to a menu item being clicked.
        /// </summary>
        /// <param name="sender">The menu item.</param>
        /// <param name="e">The event arguments.</param>
        private delegate void menuItem_ClickDelegate(object sender, EventArgs e);

        private Thread splashThread;

        /// <summary>
        /// Constructor for MainForm.
        /// </summary>
        public MainForm(
            string applicationTitle)
        {
            try
            {
                this.splashThread =
                    new Thread(new ThreadStart(this.showSplashScreen));
                this.splashThread.Start();

                InitializeComponent();

                this.WindowState = Settings.Default.WindowState;
                this.ShowInTaskbar = !(this.WindowState == FormWindowState.Minimized);

                this.Text = applicationTitle;
                this.notifyIcon1.Text = applicationTitle;

                if (Settings.Default.LoadedPlugins == null)
                {
                    Settings.Default.LoadedPlugins = new ArrayList();
                }

                foreach (string pluginID in Settings.Default.LoadedPlugins)
                {
                    this.pluginsToLoad.Add(pluginID);
                }

                Application.ApplicationExit +=
                    new EventHandler(Application_ApplicationExit);

                if (Settings.Default.PluginDirectory == "")
                {
                    Settings.Default.PluginDirectory =
                        Path.Combine(
                            Environment.CurrentDirectory,
                            "My Plugins");
                }

                // Clear out the plugins directory if requested.
                if (Settings.Default.ClearPlugins)
                {
                    if (Directory.Exists(Settings.Default.PluginDirectory))
                    {
                        Directory.Delete(Settings.Default.PluginDirectory, true);
                    }
                    Settings.Default.ClearPlugins = false;
                    Settings.Default.Save();
                }

                // Create the plugin directory if it doesn't exist.
                if (!Directory.Exists(Settings.Default.PluginDirectory))
                {
                    Directory.CreateDirectory(Settings.Default.PluginDirectory);
                }

                // Add the requests that this form needs.
                foreach (Type t in this.DesiredRequests)
                {
                    this.controller.AddRequestRouting(t, this);
                }
            }
            catch (Exception ex)
            {
                bool rethrow =
                    ExceptionPolicy.HandleException(ex, "Default Policy");
                if (rethrow)
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Called when the MainForm is shown. Initializes the plugin control.
        /// </summary>
        /// <param name="sender">The form.</param>
        /// <param name="e">The event arguments.</param>
        private void MainForm_Shown(
            object sender, 
            EventArgs e)
        {
            try
            {
                // Running in the task bar now so don't show the form until
                // double click.
                //this.Hide();

                //this.Size = Settings.Default.WindowSize;

                // Code to handle showing the logging view, if requested.
                StandardAppOptions appOptions = new StandardAppOptions();
                if (appOptions.EnableLoggingView)
                {
                    Sandia.MVCFramework.Common.StandardAppOptions options =
                        new Sandia.MVCFramework.Common.StandardAppOptions();
                    options.EnableLoggingView = true;
                    LoggingView loggingView =
                        Sandia.MVCFramework.Common.StandardAppOptions.GetALoggingView();
                    loggingView.Dock = DockStyle.Fill;
                    this.flyoutToolStrip1.AddFlyoutControl("Logging View", loggingView);
                }

                // Conditional to allow synchronization to be toggled off.
                if (Settings.Default.SyncPlugins)
                {
                    // Code to handle plug-in updates.
                    if (Directory.Exists(Settings.Default.PluginUpdateDirectory))
                    {
                        try
                        {
                            StandardPluginUpdater updater =
                                new StandardPluginUpdater(
                                    Settings.Default.PluginUpdateDirectory,
                                    Settings.Default.PluginDirectory);
                            if (updater.UpdateRequired &&
                                MessageBox.Show(
                                    "A plug-in update is available, would you like to begin updating now?",
                                    "Update available...",
                                    MessageBoxButtons.YesNo,
                                    MessageBoxIcon.Question)
                                == DialogResult.Yes)
                            {
                                updater.ApplyingChange +=
                                    new EventHandler<ApplyingChangeEventArgs>(updater_ApplyingChange);
                                updater.CopyingFile +=
                                    new EventHandler<CopyingFileEventArgs>(updater_CopyingFile);
                                updater.AppliedChange +=
                                    new EventHandler<AppliedChangeEventArgs>(updater_AppliedChange);
                                updater.UpdateProgessChanged += new OnUpdateProgressChanged(updater_UpdateProgessChanged);

                                this.syncProgressForm.ShowWithInvokeCheck();

                                BackgroundWorker updateWorker = new BackgroundWorker();
                                updateWorker.DoWork += new DoWorkEventHandler(updateWorker_DoWork);
                                updateWorker.RunWorkerCompleted +=
                                    new RunWorkerCompletedEventHandler(updateWorker_RunWorkerCompleted);
                                updateWorker.RunWorkerAsync(updater);
                            }
                            else
                            {
                                this.loadPlugins();
                            }
                        }
                        catch (Exception ex)
                        {
                            LoggingTools.SendToLog(
                                "Failed to update plugin directory, trying to load local plugins.\n\nError Details:\n" + ex.Message,
                                LoggingCategories.ERROR,
                                50);
                            this.loadPlugins();
                        }
                    }
                    else
                    {
                        if (MessageBox.Show(
                            "The plug-in source path does not exist! Would you like to reset?",
                            "Invalid plug-in source path...",
                            MessageBoxButtons.YesNo,
                            MessageBoxIcon.Warning) == DialogResult.Yes)
                        {
                            this.optionsToolStripMenuItem_Click(sender, e);
                        }
                        this.loadPlugins();
                    }
                }
                else
                {
                    // No sync, just load what's in the plugin directory.
                    this.loadPlugins();
                }
            }
            catch (Exception ex)
            {
                bool rethrow =
                    ExceptionPolicy.HandleException(ex, "Default Policy");
                if (rethrow)
                {
                    throw;
                }
            }
            finally
            {
                this.splashThread.Abort();
            }
        }

        private void showSplashScreen()
        {
            SplashScreen ss = new SplashScreen();
            ss.ShowDialog();
        }

        /// <summary>
        /// Loads the plugins in the plugin directory.
        /// </summary>
        private void loadPlugins()
        {
            this.pluginSource =
                new StandardPluginSource(Settings.Default.PluginDirectory);
            this.pluginLoader =
                new StandardPluginLoader(pluginSource);

            this.flyoutPluginControl = new PluginControl();
            this.flyoutPluginControl.PluginSelected +=
                new OnPluginSelectedChanged(pluginControl_PluginSelected);
            this.flyoutPluginControl.PluginRemoved +=
                new OnPluginSelectedChanged(pluginControl_PluginRemoved);
            this.flyoutPluginControl.SetPluginSource(
                this.pluginSource);
            //this.flyoutPluginControl.SelectPlugins(this.pluginsToLoad, false); 
            
            this.windowPluginControl = new PluginControl();
            this.windowPluginControl.PluginSelected +=
                new OnPluginSelectedChanged(pluginControl_PluginSelected);
            this.windowPluginControl.PluginRemoved +=
                new OnPluginSelectedChanged(pluginControl_PluginRemoved);
            this.windowPluginControl.SetPluginSource(
                this.pluginSource);
            this.windowPluginControl.SelectPlugins(this.pluginsToLoad, true);
            this.windowPluginControl.Dock = DockStyle.Fill;
            this.windowPluginControlForm.Text = "Available Plugins";
            this.windowPluginControlForm.FormClosing += 
                new FormClosingEventHandler(windowPluginControlForm_FormClosing);
            this.windowPluginControlForm.Controls.Add(this.windowPluginControl);

            this.flyoutToolStrip1.AddFlyoutControl("Plugins", flyoutPluginControl);

            // Add internal plug-ins
            ApplicationInfoPluginImplementation appInfoPlugin =
                new ApplicationInfoPluginImplementation();
            StandardPluginLoader localPluginLoader =
                new StandardPluginLoader(null);
            localPluginLoader.LoadPlugin(appInfoPlugin, this.controller);
            appInfoPlugin.Start();

            int count = this.pluginSource.AvailablePlugins.Count;

            if (count != 1)
            {
                this.notifyIcon1.ShowBalloonTip(
                    5000,
                    "Plug-ins loaded...",
                    "Loaded " + count + " plug-ins.",
                    ToolTipIcon.Info);
            }
            else
            {
                this.notifyIcon1.ShowBalloonTip(
                    5000,
                    "Plug-ins loaded...",
                    "Loaded " + count + " plug-in.",
                    ToolTipIcon.Info);
            }
        }

        /// <summary>
        /// Called when the plugin control window is closing. Used to cancel the
        /// close and hide the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        void windowPluginControlForm_FormClosing(
            object sender, 
            FormClosingEventArgs e)
        {
            // Only cancel and hide if the user is closing the window. 
            // Otherwise, the application will not be able to exit.
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.windowPluginControlForm.Hide();
            }
        }

        /// <summary>
        /// Called when the plugin update background worker finishes. Hides the
        /// progress bar and loads the plugins.
        /// </summary>
        /// <param name="sender">The background worker.</param>
        /// <param name="e">The event arguments.</param>
        void updateWorker_RunWorkerCompleted(
            object sender, 
            RunWorkerCompletedEventArgs e)
        {
            this.syncProgressForm.HideWithInvokeCheck();
            this.loadPlugins();
        }

        /// <summary>
        /// Performs a plugin update.
        /// </summary>
        /// <param name="sender">The background worker.</param>
        /// <param name="e">The event arguments.</param>
        void updateWorker_DoWork(
            object sender, 
            DoWorkEventArgs e)
        {
            StandardPluginUpdater updater = (StandardPluginUpdater)e.Argument;
            updater.Update();
        }

        /// <summary>
        /// Called when the plugin updater progress has changed.
        /// </summary>
        /// <param name="sender">The plugin updater.</param>
        /// <param name="percentComplete">The percent complete.</param>
        void updater_UpdateProgessChanged(
            object sender, 
            int percentComplete)
        {
            this.syncProgressForm.TotalSynchronizeProgress = percentComplete;
        }

        /// <summary>
        /// Called when the plugin updater has applied a change during a plugin
        /// update.
        /// </summary>
        /// <param name="sender">The plugin updater.</param>
        /// <param name="e">The event arguments.</param>
        void updater_AppliedChange(
            object sender, 
            AppliedChangeEventArgs e)
        {
            this.syncProgressForm.FileSynchronizeProgress = 100;
        }

        /// <summary>
        /// Called when the progress of copying a file is updated during a
        /// plugin update.
        /// </summary>
        /// <param name="sender">The plugin updater.</param>
        /// <param name="e">The event arguments.</param>
        void updater_CopyingFile(
            object sender, 
            CopyingFileEventArgs e)
        {
            this.syncProgressForm.FileSynchronizeProgress = e.PercentCopied;
        }

        /// <summary>
        /// Called when the plugin updater begins applying a change during a
        /// plugin update.
        /// </summary>
        /// <param name="sender">The plugin updater.</param>
        /// <param name="e">The event arguments.</param>
        void updater_ApplyingChange(
            object sender, 
            ApplyingChangeEventArgs e)
        {
            if (e.NewFileData != null)
            {
                this.syncProgressForm.FileSynchronizeName = e.NewFileData.Name;
            }
            else
            {
                this.syncProgressForm.FileSynchronizeName = e.CurrentFileData.Name;
            }
            this.syncProgressForm.FileSynchronizeProgress = 0;
        }

        /// <summary>
        /// Called when the MainForm is closing. Saves the current settings.
        /// </summary>
        /// <param name="sender">The form.</param>
        /// <param name="e">The event arguments.</param>
        private void MainForm_FormClosing(
            object sender, 
            FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.WindowState = FormWindowState.Minimized;
            }
            else
            {
                Settings.Default.LoadedPlugins.Clear();
                foreach (string pluginID in this.pluginsToLoad)
                {
                    Settings.Default.LoadedPlugins.Add(pluginID);
                }
            }
        }

        /// <summary>
        /// Called when the application exits. Saved the settings.
        /// </summary>
        /// <param name="sender">The application.</param>
        /// <param name="e">The event arguments.</param>
        void Application_ApplicationExit(
            object sender, 
            EventArgs e)
        {
            Settings.Default.Save();
        }

        /// <summary>
        /// Called when a plugin is unchecked in the plugin control.
        /// </summary>
        /// <param name="sender">The control that is sending this event.</param>
        /// <param name="plugin">The plugin that was removed.</param>
        private void pluginControl_PluginRemoved(
            PluginControl sender, 
            IPlugin plugin)
        {
            this.pluginLoader.RemovePlugin(plugin, this.controller);
            List<string> newPluginsToLoad = new List<string>();
            foreach (string pluginID in this.pluginsToLoad)
            {
                if (pluginID != plugin.ID)
                {
                    newPluginsToLoad.Add(pluginID);
                }
            }
            this.pluginsToLoad = newPluginsToLoad;
            // HACK JTM - These don't work for some reason, so used the above code
            // as a workaround.
            //this.pluginsToLoad.RemoveAt(this.pluginsToLoad.IndexOf(plugin.ID));
            //bool result = this.pluginsToLoad.Remove(plugin.ID);
            if (this.pluginPages.ContainsKey(plugin.ID))
            {
                foreach (TabPage tabPage in this.pluginPages[plugin.ID])
                {
                    this.tabControl.TabPages.Remove(tabPage);
                }
                this.pluginPages.Remove(plugin.ID);
            }
            this.activePlugins.Remove(plugin.ID);
            if (sender == this.flyoutPluginControl)
            {
                this.windowPluginControl.RemovePlugin(plugin, false);
            }
            else if (sender == this.windowPluginControl)
            {
                this.flyoutPluginControl.RemovePlugin(plugin, false);
            }
        }

        /// <summary>
        /// Called when a plugin is checked in the plugin control.
        /// </summary>
        /// <param name="sender">The control that is sending this event.</param>
        /// <param name="plugin">The plugin that was selected.</param>
        void pluginControl_PluginSelected(
            PluginControl sender, 
            IPlugin plugin)
        {
            this.pluginLoader.LoadPlugin(plugin, this.controller);
            this.pluginsToLoad.Add(plugin.ID);
            if (plugin.ViewManagers.Count != 0)
            {
                List<TabPage> tabPages = new List<TabPage>();
                foreach (IViewManager viewManager in plugin.ViewManagers)
                {
                    Control viewManagerControl = viewManager as Control;
                    if (viewManagerControl != null)
                    {
                        tabPages.Add(
                            this.addTabPageControl(
                                plugin.DisplayName,
                                viewManagerControl));
                    }
                }
                this.pluginPages[plugin.ID] = tabPages;
            }
            if (!this.InvokeRequired)
            {
                plugin.Start();
            }
            this.activePlugins[plugin.ID] = plugin;
            if (sender == this.flyoutPluginControl)
            {
                this.windowPluginControl.SelectPlugin(plugin, false);
            }
            else if (sender == this.windowPluginControl)
            {
                this.flyoutPluginControl.SelectPlugin(plugin, false);
            }
        }

         /// <summary>
         /// Adds a tab page with the given display name and the given control.
         /// </summary>
         /// <param name="displayName">The name to put on the tab.</param>
         /// <param name="control">The control.</param>
         /// <returns>The tab page that has been added to tabControl.</returns>
        private TabPage addTabPageControl(
            string displayName,
            Control control)
        {
            // Create the tab page.
            TabPage tabPage = new TabPage(displayName);

            // Add the control.
            control.Dock = DockStyle.Fill;
            tabPage.Controls.Add(control);

            // Add a toolstrip with a close button.
            ToolStrip toolStrip = new ToolStrip();
            toolStrip.RightToLeft = RightToLeft.Yes;
            toolStrip.Dock = DockStyle.Top;
            Assembly thisExe = Assembly.GetExecutingAssembly();
            Stream file =
                thisExe.GetManifestResourceStream("Sandia.MVCShell.closer.png");
            Image closeImage = Image.FromStream(file);
            ToolStripButton closeButton = new ToolStripButton(closeImage);
            closeButton.ImageScaling = ToolStripItemImageScaling.None;
            closeButton.Click += new EventHandler(closeButton_Click);
            toolStrip.Items.Add(closeButton);
            toolStrip.RenderMode = ToolStripRenderMode.System;
            toolStrip.GripStyle = ToolStripGripStyle.Hidden;
            tabPage.Controls.Add(toolStrip);

            // Add the tab page.
            this.tabControl.TabPages.Add(tabPage);
            return tabPage;
        }

        /// <summary>
        /// Called when the close button for a view manager's tab is clicked.
        /// Closes the tab and removes the view manager. Unloads the plugin if
        /// necessary.
        /// </summary>
        /// <param name="sender">The button.</param>
        /// <param name="e">The event arguments.</param>
        void closeButton_Click(
            object sender, 
            EventArgs e)
        {
            // Get the page.
            TabPage tabPage = (TabPage)(sender as ToolStripButton).Owner.Parent;

            // Remove the page from the tab control.
            this.tabControl.TabPages.Remove(tabPage);

            string removeID = null;
            
            // Remove the page from the list of plugin pages.
            foreach (KeyValuePair<string, List<TabPage>> plugin in this.pluginPages)
            {
                if (plugin.Value.Contains(tabPage))
                {
                    plugin.Value.Remove(tabPage);
                    if (plugin.Value.Count == 0)
                    {
                        removeID = plugin.Key;
                    }
                }
            }

            // Remove the plugin if it is empty...
            if (removeID != null)
            {
                this.flyoutPluginControl.RemovePlugin(this.activePlugins[removeID], true);
            }
        }

        /// <summary>
        /// Called when a control is pinned from the flyout toolstrip.
        /// </summary>
        /// <param name="wrapper">The control wrapper.</param>
        /// <param name="control">The control.</param>
        private void flyoutToolStrip1_ControlPinned(
            Control wrapper,
            Control control)
        {
            wrapper.Dock = DockStyle.Fill;
            if (this.mainPanel.Controls.Contains(this.splitPanel))
            {
                Control pinnedControl = this.splitPanel.Panel1.Controls[0];
                this.splitPanel.Panel1.Controls.Clear();
                SplitContainer innerSplitPanel = new SplitContainer();
                innerSplitPanel.Panel1.Controls.Add(pinnedControl);
                innerSplitPanel.Panel2.Controls.Add(wrapper);
                innerSplitPanel.Dock = DockStyle.Fill;
                this.splitPanel.Panel1.Controls.Add(innerSplitPanel);
            }
            else
            {
                this.mainPanel.Controls.Remove(this.tabControl);
                this.splitPanel = new SplitContainer();
                this.splitPanel.Panel1.Controls.Add(wrapper);
                this.splitPanel.Panel2.Controls.Add(this.tabControl);
                this.splitPanel.Dock = DockStyle.Fill;
                this.mainPanel.Controls.Add(this.splitPanel);
            }
            
        }

        /// <summary>
        /// Called when a control is unpinned from the flyout toolstrip.
        /// </summary>
        /// <param name="wrapper">The control wrapper.</param>
        /// <param name="control">The control.</param>
        private void flyoutToolStrip1_ControlUnpinned(
            Control wrapper, 
            Control control)
        {
            if (this.splitPanel.Panel1.Controls.Contains(wrapper))
            {
                this.splitPanel.Panel1.Controls.Remove(wrapper);
                this.splitPanel.Panel2.Controls.Remove(this.tabControl);
                this.mainPanel.Controls.Remove(this.splitPanel);
                wrapper.Dock = DockStyle.None;
                this.mainPanel.Controls.Add(this.tabControl);
            }
            else
            {
                SplitContainer innerSplitPanel = 
                    (SplitContainer)this.splitPanel.Panel1.Controls[0];
                Control pinnedControl;
                if (innerSplitPanel.Panel1.Controls.Contains(wrapper))
                {
                    pinnedControl = innerSplitPanel.Panel2.Controls[0];
                    innerSplitPanel.Panel1.Controls.Remove(wrapper);
                    innerSplitPanel.Panel2.Controls.Remove(pinnedControl);
                }
                else
                {
                    pinnedControl = innerSplitPanel.Panel1.Controls[0];
                    innerSplitPanel.Panel1.Controls.Remove(pinnedControl);
                    innerSplitPanel.Panel2.Controls.Remove(wrapper);
                }
                wrapper.Dock = DockStyle.None;
                this.splitPanel.Panel1.Controls.Remove(innerSplitPanel);
                this.splitPanel.Panel1.Controls.Add(pinnedControl);
            }
        }

        #region IRequestProcessor Members

        /// <summary>
        /// The IRequest types that this RequestProcessor would like to process.
        /// </summary>
        public IList<Type> DesiredRequests
        {
            get
            {
                List<Type> answer = new List<Type>();
                answer.Add(typeof(AddApplicationLevelFeatureAccessorRequest));
                answer.Add(typeof(AddViewRequest));
                answer.Add(typeof(AddPersistentViewRequest));
                answer.Add(typeof(AddViewManagerRequest));
                answer.Add(typeof(GUIThreadExecutionRequest));
                return answer;
            }
        }

        /// <summary>
        /// A delegate for functions used to process a request.
        /// </summary>
        /// <param name="request">The request to process.</param>
        protected delegate void ProcessRequestDelegate(IRequest request);

        /// <summary>
        /// Processes an incoming request
        /// </summary>
        /// <param name="request">The IRequest to process</param>
        public void ProcessRequest(
            IRequest request)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new ProcessRequestDelegate(ProcessRequest), new object[] { request });
            }
            else
            {
                AddApplicationLevelFeatureAccessorRequest aalfar = request as AddApplicationLevelFeatureAccessorRequest;
                AddViewRequest avr = request as AddViewRequest;
                AddPersistentViewRequest apvr = request as AddPersistentViewRequest;
                AddViewManagerRequest avmr = request as AddViewManagerRequest;
                GUIThreadExecutionRequest gter = request as GUIThreadExecutionRequest;
                if (aalfar != null)
                {
                    switch (aalfar.FeatureAccessorType)
                    {
                        case FeatureAccessorType.Button:
                            ToolStripButtonWithGUID tsbwg = new ToolStripButtonWithGUID(aalfar.AccessFeatureDelegate);
                            tsbwg.Text = aalfar.Name;
                            tsbwg.ToolTipText = aalfar.ToolTip;
                            if (aalfar.Image != null) tsbwg.Image = aalfar.Image;
                            this.menuStrip1.Items.Add(tsbwg);
                            break;
                        case FeatureAccessorType.MenuItem:
                            ToolStripMenuItemWithGUID tsmiwg1 = new ToolStripMenuItemWithGUID(aalfar.AccessFeatureDelegate);
                            tsmiwg1.Text = aalfar.Name;
                            tsmiwg1.ToolTipText = aalfar.ToolTip;
                            if (aalfar.Image != null) tsmiwg1.Image = aalfar.Image;
                            ToolStripMenuItemWithGUID tsmiwg2 = new ToolStripMenuItemWithGUID(aalfar.AccessFeatureDelegate);
                            tsmiwg2.Text = aalfar.Name;
                            tsmiwg2.ToolTipText = aalfar.ToolTip;
                            if (aalfar.Image != null) tsmiwg2.Image = aalfar.Image;
                            createMenuItem(tsmiwg1, tsmiwg2, aalfar.Tag);
                            break;
                    }
                }
                else if (avr != null)
                {
                    ProcessAddViewRequest(avr.ViewConstructor);
                }
                else if (apvr != null)
                {
                    // Instance the form before the view
                    //
                    Form form = new Form();

                    IView theView = apvr.ViewConstructor();
                    if (theView is UserControl)
                    {
                        // Was form.Size which was incorrect
                        //
                        form.ClientSize = ((UserControl)theView).Size;
                    }

                    form.Controls.Add((Control)theView);
                    ((Control)theView).Dock = DockStyle.Fill;
                    //Not a mdi window in this app
                    //form.MdiParent = this;

                    string uniqueIdentifier = System.Guid.NewGuid().ToString();
                    this.persistentViewForms.Add(uniqueIdentifier, form);
                    ToolStripMenuItemWithID menuItem1 = new ToolStripMenuItemWithID(apvr.Name, uniqueIdentifier);
                    menuItem1.Click += new EventHandler(menuItem_Click);
                    ToolStripMenuItemWithID menuItem2 = new ToolStripMenuItemWithID(apvr.Name, uniqueIdentifier);
                    menuItem2.Click += new EventHandler(menuItem_Click);
                    createMenuItem(menuItem1, menuItem2, apvr.CategoryName);

                    // Add the view's subscriptions to the controller.
                    foreach (Type type in theView.DesiredSubscriptions)
                    {
                        this.controller.Subscribe(theView, type);
                    }

                    // Start the view.
                    theView.Start();
                }
                else if (avmr != null)
                {
                    try
                    {
                        IViewManagerFactory factory =
                            (IViewManagerFactory)avmr[ViewManagerFactoryRequestParameter.NAME].Data;
                        string pluginID = 
                            (string)avmr[PluginIdRequestParameter.NAME].Data;

                        if (!this.pluginPages.ContainsKey(pluginID))
                        {
                            this.pluginPages[pluginID] = new List<TabPage>();
                        }

                        IViewManager viewManager = factory.CreateViewManager();

                        this.controller.AddViewManager(viewManager);
                        foreach (Type type in viewManager.DesiredSubscriptions)
                        {
                            this.controller.Subscribe(viewManager, type);
                        }

                        Control viewManagerControl = viewManager as Control;
                        if (viewManagerControl != null)
                        {
                            this.pluginPages[pluginID].Add(
                                this.addTabPageControl(
                                    this.activePlugins[pluginID].DisplayName,
                                    viewManagerControl));
                        }

                        viewManager.Start();
                    }
                    catch (Exception e)
                    {
                        this.sendErrorToLog("Error in MainForm.ProcessRequest: " +
                            e.Message);
                    }
                }
                else if (gter != null)
                {
                    ((GUIThreadExecutionRequest)request).ThreadDelegate();
                }
            }
        }

        #endregion

        /// <summary>
        /// Processes an AddViewRequest.
        /// </summary>
        /// <param name="viewConstructor">The constructor for the view.</param>
        private void ProcessAddViewRequest(
            ViewConstructor viewConstructor)
        {
            // Instance the form before the view
            //
            Form form = new Form();

            IView view = viewConstructor();
            if (view is UserControl)
            {
                // Was form.Size which was incorrect
                //
                form.ClientSize = ((UserControl)view).Size;
            }
            else
            {
                // Default form size
                //
                form.Size = new Size(600, 420);
            }

            form.Controls.Add((Control)view);
            ((Control)view).Dock = DockStyle.Fill;
            
            // Add the view's subscriptions to the controller.
            foreach (Type type in view.DesiredSubscriptions)
            {
                this.controller.Subscribe(view, type);
            }

            //not a mdi window in this app
            //form.MdiParent = this;
            form.Show();

            // Call the start method
            //
            view.Start();
        }

        /// <summary>
        /// Creates a new menu item in the main menu strip.
        /// </summary>
        /// <param name="newitem1">The new item, this can be null.</param>
        /// <param name="newitem2">The new item, this can be null.</param>
        /// <param name="menuName">The name to use if the above is null.</param>
        /// <remarks>
        /// The reason you have to pass in two menu items is because one goes into
        /// the main menu and one into the context menu. This is due to the fact
        /// that you cannot clone menu items.
        /// </remarks>
        private void createMenuItem(
            ToolStripMenuItem newitem1,
            ToolStripMenuItem newitem2,
            string menuName)
        {
            lock (this)
            {
                ToolStripItem genItem = null;
                foreach (ToolStripItem tsi in this.menuStrip1.Items)
                {
                    if ((tsi is ToolStripMenuItem) && menuName.Equals(tsi.Text))
                    {
                        genItem = tsi;
                        break;
                    }
                }

                ToolStripMenuItem item = (ToolStripMenuItem)genItem;

                if (item == null)
                {
                    item = new ToolStripMenuItem(menuName);
                    item.Name = menuName;
                    this.menuStrip1.Items.Add(item);
                }

                item.DropDown.Items.Add(newitem1);

                genItem = null;
                foreach (ToolStripItem tsi in this.contextMenuStrip1.Items)
                {
                    if ((tsi is ToolStripMenuItem) && menuName.Equals(tsi.Text))
                    {
                        genItem = tsi;
                        break;
                    }
                }

                item = (ToolStripMenuItem)genItem;

                if (item == null)
                {
                    item = new ToolStripMenuItem(menuName);
                    item.Name = menuName;
                    this.contextMenuStrip1.Items.Insert(0, item);
                }

                item.DropDown.Items.Add(newitem2);
            }
        }

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="m"></param>
        //protected override void WndProc(ref Message m)
        //{
        //    if (m.Equals(0x11))
        //    {
        //        this.exitToolStripMenuItem_Click(this, new EventArgs());
        //    }
        //    base.WndProc(ref m);
        //}

        /// <summary>
        /// Called when a view menu item is clicked. Activates the view.
        /// </summary>
        /// <param name="sender">The form.</param>
        /// <param name="e">The event arguments.</param>
        private void menuItem_Click(
            object sender, 
            EventArgs e)
        {
            if (InvokeRequired)
            {
                this.Invoke(new menuItem_ClickDelegate(menuItem_Click), new object[] { sender, e });
            }
            else
            {

                Form form =
                    this.persistentViewForms[((ToolStripMenuItemWithID)sender).UniqueID];
                form.Show();
                form.Activate();
            }
        }

        /// <summary>
        /// Called when the "Open" menu item is clicked. Opens a dialog to change
        /// the plugin directory.
        /// </summary>
        /// <param name="sender">The manu item.</param>
        /// <param name="e">The event arguments.</param>
        private void openToolStripMenuItem_Click(
            object sender, 
            EventArgs e)
        {
            FolderBrowserDialog folderDialog = new FolderBrowserDialog();
            if (folderDialog.ShowDialog() == DialogResult.OK)
            {
                Settings.Default.PluginDirectory = folderDialog.SelectedPath;
            }
        }
        
        /// <summary>
        /// Sends an error message to the log.
        /// </summary>
        /// <param name="message">The message to send.</param>
        private void sendErrorToLog(
            String message)
        {
            LogEntry logEntry = new LogEntry();
            logEntry.Message = message;
            logEntry.Priority = 100;
            logEntry.Categories.Add("Error");
            Logger.Write(logEntry);
        }

        /// <summary>
        /// A ToolStripButton that has a GUID.
        /// </summary>
        private class ToolStripButtonWithGUID : ToolStripButton
        {
            /// <summary>
            /// The internal GUID.
            /// </summary>
            private string guid = System.Guid.NewGuid().ToString();

            /// <summary>
            /// The delegate to call when this button is clicked.
            /// </summary>
            private FeatureAccessorDelegate featureAccessor;

            /// <summary>
            /// Constructor for ToolStripButtonWithGUID.
            /// </summary>
            /// <param name="featureAccessor">The</param>
            public ToolStripButtonWithGUID(
                FeatureAccessorDelegate featureAccessor)
                : base()
            {
                this.featureAccessor = featureAccessor;
                this.Click += new EventHandler(toolButtonWithGUID_Click);
            }

            /// <summary>
            /// The GUID for this button.
            /// </summary>
            public string GUID
            {
                get { return guid; }
            }

            /// <summary>
            /// Called when this button is clicked.
            /// </summary>
            /// <param name="sender">This button.</param>
            /// <param name="e">The event arguments.</param>
            private void toolButtonWithGUID_Click(
                object sender, 
                EventArgs e)
            {
                // Call the delegate.
                this.featureAccessor();
            }
        }

        /// <summary>
        /// A ToolStripButton that has a GUID.
        /// </summary>
        private class ToolStripMenuItemWithGUID : ToolStripMenuItem
        {
            /// <summary>
            /// The internal GUID.
            /// </summary>
            private string guid = System.Guid.NewGuid().ToString();

            /// <summary>
            /// The delegate to call when this button is clicked.
            /// </summary>
            private FeatureAccessorDelegate featureAccessor;

            /// <summary>
            /// Constructor for ToolStripButtonWithGUID.
            /// </summary>
            /// <param name="featureAccessor">The</param>
            public ToolStripMenuItemWithGUID(
                FeatureAccessorDelegate featureAccessor)
                : base()
            {
                this.featureAccessor = featureAccessor;
                this.Click += new EventHandler(toolButtonWithGUID_Click);
            }

            /// <summary>
            /// The GUID for this button.
            /// </summary>
            public string GUID
            {
                get { return guid; }
            }

            /// <summary>
            /// Called when this button is clicked.
            /// </summary>
            /// <param name="sender">This button.</param>
            /// <param name="e">The event arguments.</param>
            private void toolButtonWithGUID_Click(
                object sender, 
                EventArgs e)
            {
                // Call the delegate.
                this.featureAccessor();
            }
        }

        /// <summary>
        /// A tool strip menu item that carries with it an additional
        /// unique identifier so it knows which item to re-open.
        /// </summary>
        private class ToolStripMenuItemWithID : ToolStripMenuItem
        {
            private string id;

            /// <summary>
            /// Create a new ToolStripMenuItemWithID
            /// </summary>
            /// <param name="name"></param>
            /// <param name="id"></param>
            public ToolStripMenuItemWithID(string name, string id)
                : base(name)
            {
                this.Name = name;
                this.id = id;
            }

            /// <summary>
            /// A unique identifier
            /// </summary>
            public string UniqueID
            {
                get { return id; }
            }
        }

        /// <summary>
        /// Called when the File -> Exit menu item is clicked. Exits the 
        /// application.
        /// </summary>
        /// <param name="sender">The menu item.</param>
        /// <param name="e">The event arguments.</param>
        private void exitToolStripMenuItem_Click(
            object sender, 
            EventArgs e)
        {
            Application.Exit();
        }

        /// <summary>
        /// Called when the Tools -> Options menu item is clicked. Opens the 
        /// options dialog
        /// </summary>
        /// <param name="sender">The menu item.</param>
        /// <param name="e">The event arguments.</param>
        private void optionsToolStripMenuItem_Click(
            object sender, 
            EventArgs e)
        {
            OptionsDialog optionsDialog = new OptionsDialog();
            optionsDialog.ShowDialog();
        }

        /// <summary>
        /// Called when the Help Topics menu item is clicked. Shows the help.
        /// </summary>
        /// <param name="sender">The menu item.</param>
        /// <param name="e">The event arguments.</param>
        private void helpTopicsToolStripMenuItem_Click(
            object sender, 
            EventArgs e)
        {
            Help.ShowHelp(this, "MVCShell.chm");
        }

        /// <summary>
        /// Called when the notify icon is double clicked in the task bar. Shows
        /// this form.
        /// </summary>
        /// <param name="sender">The notify icon.</param>
        /// <param name="e">The event arguments.</param>
        private void notifyIcon1_MouseDoubleClick(
            object sender, 
            MouseEventArgs e)
        {
            this.WindowState = this.shownWindowState;
            this.Show();
        }

        private FormWindowState shownWindowState = FormWindowState.Normal;

        /// <summary>
        /// Called when the this form is resized.
        /// </summary>
        /// <param name="sender">This form.</param>
        /// <param name="e">The event arguments.</param>
        private void MainForm_Resize(
            object sender, 
            EventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                Settings.Default.WindowSize = this.Size;
                this.shownWindowState = FormWindowState.Normal;
            }
            else if (this.WindowState == FormWindowState.Maximized)
            {
                this.shownWindowState = FormWindowState.Maximized;
            }
            this.ShowInTaskbar = !(this.WindowState == FormWindowState.Minimized);
            Settings.Default.WindowState = this.WindowState;
        }

        private void resetPluginsToolStripMenuItem_Click(
            object sender, 
            EventArgs e)
        {
            if (MessageBox.Show(
                "The application must restart in order to complete this operation. Would you like to continue?", 
                "Application requires restart!", 
                MessageBoxButtons.YesNo, 
                MessageBoxIcon.Question, 
                MessageBoxDefaultButton.Button1) == DialogResult.Yes)
            {
                Settings.Default.ClearPlugins = true;
                Settings.Default.Save();
                Application.Restart();
            }
        }

        private void pluginsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.windowPluginControlForm.Show();
        }
    }
}